library ieee;
use ieee.std_logic_1164.all;

-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
use ieee.numeric_std.all;

-- Uncomment the following library declaration if instantiating
-- any Xilinx leaf cells in this code.
--library UNISIM;
--use UNISIM.VComponents.all;

entity ssd_controller is
    generic (
        -- cycle constants
        CYCLES_TOTAL     : integer := 16e5; -- 16 ms / 10ns
        CYCLES_PER_ANODE : integer := 4e5; -- 16e5 / 4
        -- segment enables (anode signals)
        SEG_LOW  : integer := 0;
        SEG_HIGH : integer := 3;
        -- individual segments
        SEG_MIN : integer := 0;
        SEG_MAX : integer := 6
    );
    port (
        clk : in std_logic;
        res : in std_logic;

        tick     : in std_logic;
        cnt_secs : in integer;

        seg_n : out std_logic_vector(SEG_MAX downto SEG_MIN);
        dp_n : out std_logic;
        an_n : out std_logic_vector(SEG_HIGH downto SEG_LOW)
    );
end ssd_controller;

architecture Behavioral of ssd_controller is
    -- type declarations
    type modulus_lookup_t is array (SEG_LOW to SEG_HIGH) of integer;

    -- subtype declarations
    subtype seg_n_t is std_logic_vector(SEG_MAX downto SEG_MIN);

    constant INITIAL_ANODE_INTERNAL : std_logic_vector(SEG_HIGH downto SEG_LOW) := (SEG_LOW => '0', others => '1');
    constant INITIAL_ANODE_CYCLES : integer := 0;
    constant INITIAL_ANODE_INDEX : integer := 0;

    -- no better solution, since (non constant) exponentiation is not supported
    constant modulus_lookup : modulus_lookup_t := (
        1e0, 1e1, 1e2, 1e3
    );

    -- number segment constants
    -- constants have to be flipped GFEDCBA
    constant SYMBOL_0 : seg_n_t := "1000000";
    constant SYMBOL_1 : seg_n_t := "1111001";
    constant SYMBOL_2 : seg_n_t := "0100100";
    constant SYMBOL_3 : seg_n_t := "0110000";
    constant SYMBOL_4 : seg_n_t := "0011001";
    constant SYMBOL_5 : seg_n_t := "0010010";
    constant SYMBOL_6 : seg_n_t := "0000010";
    constant SYMBOL_7 : seg_n_t := "1111000";
    constant SYMBOL_8 : seg_n_t := "0000000";
    constant SYMBOL_9 : seg_n_t := "0010000";

    -- invalid segment constant
    constant SYMBOL_NONE : seg_n_t := "1111111";

    signal anode_internal_n      : std_logic_vector(SEG_HIGH downto SEG_LOW) := INITIAL_ANODE_INTERNAL;
    signal anode_internal_next_n : std_logic_vector(SEG_HIGH downto SEG_LOW) := INITIAL_ANODE_INTERNAL;
    signal anode_switch          : std_logic := '0';
    signal anode_index           : integer range SEG_LOW to SEG_HIGH := SEG_LOW;
    signal anode_index_next      : integer range SEG_LOW to SEG_HIGH := SEG_LOW;

    signal cnt_anode_cycles : integer range 0 to CYCLES_PER_ANODE;

    -- helper function to assign segment for currently active indexed segment
    pure function return_seg_n(signal cnt_secs : integer; anode_index : integer)
    return seg_n_t is
        variable ret : seg_n_t;
    begin
        -- extract currently indexed digit and assign appropriate segment constant
        if cnt_secs < modulus_lookup(anode_index) then
            ret := SYMBOL_NONE;
        else
            case (cnt_secs / modulus_lookup(anode_index)) mod 10 is
                when 0 => ret := SYMBOL_0;
                when 1 => ret := SYMBOL_1;
                when 2 => ret := SYMBOL_2;
                when 3 => ret := SYMBOL_3;
                when 4 => ret := SYMBOL_4;
                when 5 => ret := SYMBOL_5;
                when 6 => ret := SYMBOL_6;
                when 7 => ret := SYMBOL_7;
                when 8 => ret := SYMBOL_8;
                when 9 => ret := SYMBOL_9;
                when others => ret := SYMBOL_NONE;
            end case;
        end if;

        -- return return value
        return ret;
    end return_seg_n;

begin

    seg_n <= return_seg_n(cnt_secs, anode_index);
    dp_n <= '1';
    an_n <= anode_internal_n;

    internal : process (clk, res)
    begin
        -- typical register behaviour
        if res = '1' then
            anode_internal_n <= INITIAL_ANODE_INTERNAL;
            anode_index <= INITIAL_ANODE_INDEX;
        elsif rising_edge(clk) then
            anode_internal_n <= anode_internal_next_n;
            anode_index <= anode_index_next;
        end if;
    end process;

    count : process (all)
    begin
        -- default assignment
        cnt_anode_cycles <= cnt_anode_cycles;
        anode_switch <= anode_switch;

        if res = '1' then
            -- reset to some initial value on reset
            anode_switch <= '0';
            cnt_anode_cycles <= INITIAL_ANODE_CYCLES;
        elsif rising_edge(clk) then
            -- count until limit is reached, then repeat
            if cnt_anode_cycles = CYCLES_PER_ANODE then
                anode_switch <= '1';
                cnt_anode_cycles <= 0;
            else
                anode_switch <= '0';
                cnt_anode_cycles <= cnt_anode_cycles + 1;
            end if;
        end if;
    end process;

    cycle_anode : process (all)
    begin
        -- default assignment
        anode_internal_next_n <= anode_internal_next_n;
        anode_index_next <= anode_index_next;

        -- in case anodes should be cycled
        if anode_switch = '1' then
            -- cycle all but the first
            -- 1 => 0, 2 => 1, 3 => 2
            for i in SEG_LOW + 1 to SEG_HIGH loop
                anode_internal_next_n(i) <= anode_internal_n(i - 1);
            end loop;
            -- cycle the first without generics
            -- 0 => 3
            anode_internal_next_n(SEG_LOW) <= anode_internal_n(SEG_HIGH);

            -- cycle active anode index as well (necessary for calculations)
            anode_index_next <= (anode_index + 1) mod (SEG_HIGH + 1);
        end if;
    end process;

end Behavioral;
